404 template not found


The template is set for this widget, but that template was not found in the skin.

template: mini_player_global.tpl
directory: jrPlaylist
}, stop: function() { console.log('MiniPlayer: Stop via jPlayer'); $('#mini_player_jp').jPlayer("stop"); // Clear playback state when stopped try { localStorage.removeItem('miniPlayerState'); } catch(e) { console.warn('Could not clear playback state:', e); } } }; // If there was a pending track queued before jPlayer was ready, play it now if (window.MiniPlayer.pendingTrack) { console.log('MiniPlayer: Found pending track, playing now'); console.log('Pending track data:', window.MiniPlayer.pendingTrack); var pending = window.MiniPlayer.pendingTrack; window.MiniPlayer.pendingTrack = null; window.MiniPlayer.loadTrack(pending.track, pending.autoPlay); } else { console.log('MiniPlayer: No pending track to play'); } $(document).ready(function(){ console.log('MINI PLAYER: Initializing jPlayer...'); // Progress Ring Animation Variables var progressRing = document.getElementById('progress-ring-fill'); var progressRingCircumference = 2 * Math.PI * 29; // r=29 // Color Adaptive Theming var currentThemeColor = '#1db954'; function extractColorFromImage(imgElement) { try { // Create a canvas to extract colors var canvas = document.createElement('canvas'); var ctx = canvas.getContext('2d'); canvas.width = 1; canvas.height = 1; // Draw the image ctx.drawImage(imgElement, 0, 0, 1, 1); // Get the pixel data var pixelData = ctx.getImageData(0, 0, 1, 1).data; // Convert to hex var r = pixelData[0]; var g = pixelData[1]; var b = pixelData[2]; // Enhance saturation for better visual effect var max = Math.max(r, g, b); var min = Math.min(r, g, b); var saturation = max === 0 ? 0 : (max - min) / max; if (saturation < 0.3) { // Boost saturation if image is too gray var boost = 1.5; var avg = (r + g + b) / 3; r = Math.min(255, Math.floor(avg + (r - avg) * boost)); g = Math.min(255, Math.floor(avg + (g - avg) * boost)); b = Math.min(255, Math.floor(avg + (b - avg) * boost)); } return 'rgb(' + r + ',' + g + ',' + b + ')'; } catch(e) { console.warn('Color extraction failed:', e); return '#1db954'; // Default green } } function applyThemeColor(color) { if (!color || color === currentThemeColor) return; currentThemeColor = color; console.log('Applying theme color:', color); // Update progress ring gradient var gradient = document.querySelector('#progressGradient'); if (gradient) { var stops = gradient.querySelectorAll('stop'); if (stops.length >= 2) { stops[0].setAttribute('style', 'stop-color:' + color + ';stop-opacity:1'); // Create lighter version for second stop var rgb = color.match(/\d+/g); if (rgb) { var lighterColor = 'rgb(' + Math.min(255, parseInt(rgb[0]) + 30) + ',' + Math.min(255, parseInt(rgb[1]) + 30) + ',' + Math.min(255, parseInt(rgb[2]) + 30) + ')'; stops[1].setAttribute('style', 'stop-color:' + lighterColor + ';stop-opacity:1'); } } } // Keep mini player background consistently dark - no color changes var miniPlayer = document.getElementById('mini-player'); if (miniPlayer) { // Always use the same dark background regardless of artwork miniPlayer.style.background = 'rgba(18,18,18,0.98)'; } // Keep waveform background consistently dark - no color changes var waveformContainer = document.querySelector('.mini-player-waveform'); if (waveformContainer) { // Always use the same dark background with light blue accent waveformContainer.style.background = 'linear-gradient(90deg, rgba(135,206,250,0.06) 0%, rgba(135,206,250,0.02) 100%)'; waveformContainer.style.backgroundColor = 'rgba(12,12,12,0.6)'; waveformContainer.style.borderColor = 'rgba(135,206,250,0.15)'; } } function updateProgressRing(progress) { if (!progressRing) return; // Progress is 0 to 1 var offset = progressRingCircumference - (progress * progressRingCircumference); progressRing.style.strokeDashoffset = offset; } // Initialize Audio Waveform Visualizer (Live) var waveformCanvas = document.getElementById('waveform-canvas'); var waveformCtx = waveformCanvas ? waveformCanvas.getContext('2d') : null; var audioContext = null; var analyser = null; var dataArray = null; var bufferLength = 0; var animationId = null; var hoverX = -1; // Initialize Progress Waveform (Static) var progressWaveformCanvas = document.getElementById('progress-waveform-canvas'); var progressWaveformCtx = progressWaveformCanvas ? progressWaveformCanvas.getContext('2d') : null; var progressWaveformData = []; var progressHoverX = -1; function initWaveform(audioElement) { if (!waveformCanvas || !waveformCtx) return; if (audioContext) return; // Already initialized try { // Create audio context and analyser audioContext = new (window.AudioContext || window.webkitAudioContext)(); analyser = audioContext.createAnalyser(); analyser.fftSize = 128; // Smaller for performance analyser.smoothingTimeConstant = 0.8; // IMPORTANT: Only create source once var source = audioContext.createMediaElementSource(audioElement); source.connect(analyser); analyser.connect(audioContext.destination); bufferLength = analyser.frequencyBinCount; dataArray = new Uint8Array(bufferLength); // Set canvas size waveformCanvas.width = waveformCanvas.offsetWidth; waveformCanvas.height = waveformCanvas.offsetHeight; console.log('Waveform: Initialized successfully'); drawWaveform(); } catch(e) { console.error('Waveform: Initialization failed', e); // Continue without waveform if it fails audioContext = null; analyser = null; } } function drawWaveform() { if (!analyser || !waveformCtx || !dataArray) return; animationId = requestAnimationFrame(drawWaveform); analyser.getByteFrequencyData(dataArray); var width = waveformCanvas.width; var height = waveformCanvas.height; // Clear canvas waveformCtx.clearRect(0, 0, width, height); // Draw bars var barWidth = (width / bufferLength) * 2.0; var barHeight; var x = 0; for(var i = 0; i < bufferLength; i++) { barHeight = (dataArray[i] / 255) * height * 0.8; // Create gradient with consistent light blue color var gradient = waveformCtx.createLinearGradient(0, height - barHeight, 0, height); gradient.addColorStop(0, '#87CEEB'); // Light blue (skyblue) gradient.addColorStop(1, '#ADD8E6'); // Lighter blue waveformCtx.fillStyle = gradient; waveformCtx.fillRect(x, height - barHeight, barWidth, barHeight); x += barWidth + 1; } } function stopWaveform() { if (animationId) { cancelAnimationFrame(animationId); animationId = null; } if (waveformCtx && waveformCanvas) { waveformCtx.clearRect(0, 0, waveformCanvas.width, waveformCanvas.height); } } // Enhanced waveform patterns - Based on real mastered songs var waveformPatterns = { // Pattern 1: Modern Pop/Rock - Dynamic with variation modernPop: function(samples) { var data = []; for (var i = 0; i < samples; i++) { var progress = i / samples; // Intro/verse/chorus/bridge structure with MORE dynamics var section = Math.floor(progress * 8); var sectionType = section % 4; var baseLevel; if (sectionType === 0) baseLevel = 0.55; // Intro - quieter else if (sectionType === 1) baseLevel = 0.75; // Verse - moderate else if (sectionType === 2) baseLevel = 0.88; // Chorus - loud else baseLevel = 0.68; // Bridge - medium // More pronounced beats var beat = (i % 8 < 1) ? 0.15 : (i % 8 === 4) ? 0.1 : 0; // Enhanced melodic variation var melody = Math.abs(Math.sin(i / 7) * Math.cos(i / 11)) * 0.2; // Micro dynamics for realism var microDynamics = (Math.random() - 0.5) * 0.08; var level = baseLevel + beat + melody + microDynamics; data.push(Math.min(0.95, Math.max(0.45, level))); } return data; }, // Pattern 2: Electronic/EDM - Enhanced dynamics with dramatic builds edm: function(samples) { var data = []; for (var i = 0; i < samples; i++) { var progress = i / samples; // More dramatic build-ups and drops var buildSection = (progress < 0.22 || (progress > 0.5 && progress < 0.72)); var dropSection = (progress > 0.22 && progress < 0.28) || (progress > 0.72 && progress < 0.78); if (buildSection) { // Gradual build with tension var localProgress = (progress < 0.22) ? (progress / 0.22) : ((progress - 0.5) / 0.22); var tension = Math.pow(localProgress, 1.5); // Exponential build var hihat = Math.abs(Math.sin(i / 2)) * 0.12 * localProgress; data.push(0.35 + (tension * 0.52) + hihat); } else if (dropSection) { // The drop - more variation var dropProgress = (progress < 0.28) ? ((progress - 0.22) / 0.06) : ((progress - 0.72) / 0.06); data.push(0.18 + Math.random() * 0.15 + (Math.sin(dropProgress * Math.PI * 4) * 0.1)); } else { // Full energy sections with more variation var energy = 0.78 + Math.abs(Math.sin(i / 3.5) * Math.cos(i / 5)) * 0.18; var kick = (i % 4 === 0) ? 0.12 : 0; var snare = (i % 8 === 4) ? 0.08 : 0; data.push(Math.min(0.96, energy + Math.max(kick, snare))); } } return data; }, // Pattern 3: Classical/Orchestral - MAXIMUM dynamics, dramatic crescendos orchestral: function(samples) { var data = []; for (var i = 0; i < samples; i++) { var progress = i / samples; // Dramatic multi-movement structure var movement = Math.floor(progress * 3); // Overall phrase arc with more drama var phrase = Math.sin(progress * Math.PI * 2.5) * 0.45 + 0.45; // Multiple crescendos and diminuendos var crescendo = Math.pow(Math.sin(progress * Math.PI * 5), 3) * 0.35; // String section swells var strings = Math.abs(Math.sin(i / 12) * Math.cos(i / 9)) * 0.25; // Quiet passages - more dramatic var quietSection = (progress > 0.25 && progress < 0.35) || (progress > 0.65 && progress < 0.72); var quietness = quietSection ? -0.45 : 0; // Climactic moments var climax = (progress > 0.45 && progress < 0.52) || (progress > 0.85 && progress < 0.92); var climaxBoost = climax ? 0.25 : 0; // Natural orchestral breathing var natural = (Math.random() - 0.5) * 0.12; var level = phrase + crescendo + strings + quietness + climaxBoost + natural; data.push(Math.min(0.98, Math.max(0.08, level))); } return data; }, // Pattern 4: Hip-Hop/R&B - More dynamic with bass drops and vocal peaks hiphop: function(samples) { var data = []; for (var i = 0; i < samples; i++) { var progress = i / samples; // Intro/verse/hook/outro structure var section = Math.floor(progress * 8); var isIntro = section === 0; var isVerse = section === 1 || section === 3 || section === 5; var isHook = section === 2 || section === 4 || section === 6; var isOutro = section === 7; var baseLevel; if (isIntro) baseLevel = 0.45; else if (isVerse) baseLevel = 0.62; else if (isHook) baseLevel = 0.82; else baseLevel = 0.50; // outro // 808 bass slides and hits var bassHit = (i % 16 === 0) ? 0.18 : (i % 16 === 7) ? 0.14 : 0; var bassSlide = Math.abs(Math.sin(i / 9)) * 0.1; // Drum pattern - more pronounced var kick = (i % 8 === 0) ? 0.15 : 0; var snare = (i % 8 === 4) ? 0.13 : 0; // Hi-hat rolls var hihat = (i % 2 === 0) ? Math.abs(Math.sin(i / 2.5)) * 0.09 : 0; // Vocal ad-libs and harmonies var vocal = Math.abs(Math.sin(i / 13) * Math.cos(i / 17)) * 0.15; var level = baseLevel + Math.max(bassHit, kick, snare) + bassSlide + hihat + vocal; data.push(Math.min(0.94, Math.max(0.38, level + (Math.random() - 0.5) * 0.06))); } return data; }, // Pattern 5: Acoustic/Singer-Songwriter - Enhanced natural dynamics acoustic: function(samples) { var data = []; for (var i = 0; i < samples; i++) { var progress = i / samples; // Verse-chorus-bridge structure with MORE contrast var section = Math.floor(progress * 6); var isVerse = section === 0 || section === 2 || section === 4; var isChorus = section === 1 || section === 3; var isBridge = section === 5; var baseStructure; if (isVerse) baseStructure = 0.38 + Math.sin(progress * Math.PI * 4) * 0.18; else if (isChorus) baseStructure = 0.65 + Math.sin(progress * Math.PI * 3) * 0.22; else baseStructure = 0.48; // bridge // Guitar strumming with more attack var strum = (i % 6 === 0) ? 0.16 : (i % 6 === 3) ? 0.09 : Math.abs(Math.sin(i / 3.5)) * 0.06; // Vocal phrasing - more dramatic peaks var vocal = Math.abs(Math.sin(i / 11) * Math.cos(i / 14)) * 0.28; // Fingerpicking detail var picking = (i % 4 === 0) ? 0.08 : 0; // Natural breathing and dynamics var breath = Math.sin(i / 18) * 0.14; // Organic variations var variation = (Math.random() - 0.5) * 0.12; var level = baseStructure + strum + vocal + picking + breath + variation; data.push(Math.min(0.92, Math.max(0.12, level))); } return data; }, // Pattern 6: Rock/Metal - DRAMATIC dynamic contrast rock: function(samples) { var data = []; for (var i = 0; i < samples; i++) { var progress = i / samples; // Intro/verse/pre-chorus/chorus/breakdown/solo/chorus/outro var section = Math.floor(progress * 8); var baseEnergy; if (section === 0) baseEnergy = 0.42; // Intro - building else if (section === 1 || section === 5) baseEnergy = 0.58; // Verse - moderate else if (section === 2) baseEnergy = 0.72; // Pre-chorus - building else if (section === 3 || section === 6) baseEnergy = 0.89; // Chorus - explosive else if (section === 4) baseEnergy = 0.35; // Breakdown - quiet else baseEnergy = 0.78; // Outro - heavy // Double bass drum patterns var kick = (i % 2 === 0) ? 0.14 : 0.08; // Snare with ghost notes var snare = (i % 4 === 2) ? 0.16 : (i % 8 === 6) ? 0.07 : 0; // Power chord chugs var guitar = Math.abs(Math.sin(i / 4.5) * Math.cos(i / 6)) * 0.18; // Guitar harmonics in quiet parts var harmonics = (section === 4) ? Math.abs(Math.sin(i / 8)) * 0.15 : 0; // Cymbal crashes and rides var cymbals = (i % 16 === 0 && section >= 3) ? 0.14 : Math.abs(Math.sin(i / 3)) * 0.06; // Bass guitar presence var bass = Math.abs(Math.sin(i / 7)) * 0.12; var level = baseEnergy + Math.max(kick, snare) + guitar + harmonics + cymbals + bass; data.push(Math.min(0.97, Math.max(0.28, level + (Math.random() - 0.5) * 0.07))); } return data; } }; var waveformCaptureInterval = null; var currentWaveformPattern = null; function generateProgressWaveform() { if (!progressWaveformCanvas) return; // Clear previous capture if (waveformCaptureInterval) { clearInterval(waveformCaptureInterval); } // Set canvas size progressWaveformCanvas.width = progressWaveformCanvas.offsetWidth; progressWaveformCanvas.height = progressWaveformCanvas.offsetHeight; // Select a realistic waveform pattern var patternNames = ['modernPop', 'edm', 'orchestral', 'hiphop', 'acoustic', 'rock']; var randomPattern = patternNames[Math.floor(Math.random() * patternNames.length)]; currentWaveformPattern = randomPattern; console.log('Progress Waveform: Using realistic pattern -', randomPattern); // Generate the waveform with more samples for detailed appearance (increased to 400 for longer/denser waveform) progressWaveformData = waveformPatterns[randomPattern](400); drawProgressWaveform(); // Real-time audio capture disabled to maintain consistent waveform // Using realistic fallback patterns only for stable, predictable appearance console.log('Progress Waveform: Using static realistic pattern (real-time capture disabled)'); } // Draw progress waveform with progress coloring function drawProgressWaveform() { if (!progressWaveformCtx || !progressWaveformCanvas) return; var width = progressWaveformCanvas.width; var height = progressWaveformCanvas.height; // Clear canvas progressWaveformCtx.clearRect(0, 0, width, height); // If no waveform data, just return (canvas will be empty) if (progressWaveformData.length === 0) return; // Get playback progress var currentTime = miniPlayer.data('jPlayer') ? miniPlayer.data('jPlayer').status.currentTime : 0; var duration = miniPlayer.data('jPlayer') ? miniPlayer.data('jPlayer').status.duration : 0; var progress = duration > 0 ? (currentTime / duration) : 0; var progressX = width * progress; // Draw waveform bars - optimized for 400 samples (denser, longer appearance) var barCount = progressWaveformData.length; var barWidth = (width / barCount) * 0.82; var gap = (width / barCount) * 0.18; for (var i = 0; i < barCount; i++) { var x = i * (barWidth + gap); // Enhanced bar height calculation with micro-variations for realism var baseHeight = progressWaveformData[i] * height * 0.85; // Add subtle random variation to mimic analog waveform (�2%) var microVariation = (Math.random() - 0.5) * 0.04; var barHeight = baseHeight * (1 + microVariation); var y = (height - barHeight) / 2; // Determine color based on progress if (x < progressX) { // Played section - light blue with slight alpha variation for depth var alpha = 0.95 + (Math.random() * 0.05); progressWaveformCtx.fillStyle = 'rgba(135, 206, 250, ' + alpha + ')'; // Light blue (skyblue) } else { // Unplayed section - dimmed gray with slight variation var alpha = 0.28 + (Math.random() * 0.04); progressWaveformCtx.fillStyle = 'rgba(255, 255, 255, ' + alpha + ')'; } // Draw bar with rounded tops for smoother appearance progressWaveformCtx.beginPath(); var radius = Math.min(barWidth / 2, 1); progressWaveformCtx.moveTo(x, y + radius); progressWaveformCtx.arcTo(x, y, x + barWidth, y, radius); progressWaveformCtx.lineTo(x + barWidth, y + barHeight - radius); progressWaveformCtx.arcTo(x + barWidth, y + barHeight, x, y + barHeight, radius); progressWaveformCtx.lineTo(x, y + radius); progressWaveformCtx.fill(); } // Draw hover preview line if (progressHoverX >= 0) { progressWaveformCtx.strokeStyle = 'rgba(255, 255, 255, 0.6)'; progressWaveformCtx.lineWidth = 2; progressWaveformCtx.setLineDash([4, 4]); progressWaveformCtx.beginPath(); progressWaveformCtx.moveTo(progressHoverX, 0); progressWaveformCtx.lineTo(progressHoverX, height); progressWaveformCtx.stroke(); progressWaveformCtx.setLineDash([]); } } // Initialize jPlayer instance var miniPlayer = $('#mini_player_jp'); miniPlayer.jPlayer({ swfPath: "http://mediaistream.com/modules/jrCore/contrib/jplayer", ready: function() { console.log('Mini Player: jPlayer ready'); // Initialize waveform once - MUST be done only once per audio element var audioElement = $('#mini_player_jp audio')[0]; if (audioElement && !audioContext) { console.log('Mini Player: Initializing waveform'); initWaveform(audioElement); } return true; }, cssSelectorAncestor: "#mini_player_container", supplied: 'mp3,oga', solution: "html,flash", volume: 0.7, wmode: 'window', consoleAlerts: true, preload: 'none', play: function() { miniPlayer.jPlayer("pauseOthers"); // Resume audio context if it's suspended if (audioContext) { if (audioContext.state === 'suspended') { audioContext.resume().then(function() { console.log('Waveform: AudioContext resumed'); }); } // Resume waveform animation if (analyser && !animationId) { drawWaveform(); } } }, pause: function() { // Clear auto-resume when user manually pauses console.log('Mini Player: Paused by user'); // Stop waveform animation stopWaveform(); try { var savedState = localStorage.getItem('miniPlayerState'); if (savedState) { var state = JSON.parse(savedState); state.isPlaying = false; localStorage.setItem('miniPlayerState', JSON.stringify(state)); } } catch(e) { console.warn('Could not update pause state:', e); } }, ended: function() { console.log('Mini Player: Track ended'); stopWaveform(); // Play next track from queue if (window.QueueManager) { var nextTrack = window.QueueManager.getNextTrack(); if (nextTrack && window.MiniPlayerJPlayer) { window.MiniPlayerJPlayer.loadAndPlay(nextTrack); } } }, timeupdate: function(event) { // Update progress bar and time display if (event.jPlayer.status.duration) { // Calculate progress var progress = event.jPlayer.status.currentTime / event.jPlayer.status.duration; // Update progress ring updateProgressRing(progress); // Update progress waveform drawProgressWaveform(); // Use jPlayer's built-in time conversion var currentTimeFormatted = $.jPlayer.convertTime(event.jPlayer.status.currentTime); var durationFormatted = $.jPlayer.convertTime(event.jPlayer.status.duration); $('#mini_player_current_time').text(currentTimeFormatted); $('#mini_player_duration').text(durationFormatted); // Save playback state for continuous play across pages if (window.MiniPlayerJPlayer && window.MiniPlayerJPlayer.currentTrack) { var playbackState = { track: window.MiniPlayerJPlayer.currentTrack, position: event.jPlayer.status.currentTime, isPlaying: !event.jPlayer.status.paused, timestamp: Date.now() }; try { localStorage.setItem('miniPlayerState', JSON.stringify(playbackState)); } catch(e) { console.warn('Could not save playback state:', e); } } } }, error: function(event) { console.error('jPlayer Error:', event.jPlayer.error); console.error('jPlayer Error Type:', event.jPlayer.error.type); console.error('jPlayer Error Context:', event.jPlayer.error.context); console.error('jPlayer Error Message:', event.jPlayer.error.message); } }); // Play button click handler - like jrAudio_button var playBtn = $('#mini_player_play_btn'); playBtn.click(function(e) { console.log('Mini Player: Play button clicked - will be set by loadAndPlay'); e.preventDefault(); }); // Mini Player Controller - jPlayer based window.MiniPlayerJPlayer = { currentTrack: null, loadAndPlay: function(track) { console.log('=== jPlayer Mini Player: loadAndPlay ==='); console.log('Full track object:', track); console.log('Track ID:', track.id); console.log('Track title:', track.title); console.log('Track artist:', track.artist); console.log('Track artwork:', track.artwork); console.log('Track audioUrl:', track.audioUrl); // Check if track.id exists, if not try track._item_id var trackId = track.id || track._item_id || track.item_id; console.log('Resolved track ID:', trackId); if (!track || !trackId) { console.error('jPlayer Mini Player: Invalid track - missing ID'); console.error('Track object keys:', Object.keys(track)); return; } this.currentTrack = track; // Add track change animation var $miniPlayer = $('#mini-player'); $miniPlayer.addClass('loading'); // Fade out info, update, fade in var $title = $('#mini_player_title'); var $artist = $('#mini_player_artist'); $title.css('opacity', '0').text(track.title || 'Unknown Track'); $artist.css('opacity', '0').text(track.artist || 'Unknown Artist'); setTimeout(function() { $title.css({'opacity': '1', 'transition': 'opacity 0.4s ease'}); $artist.css({'opacity': '1', 'transition': 'opacity 0.4s ease'}); $miniPlayer.removeClass('loading'); }, 100); console.log('jPlayer Mini Player: Title and Artist updated with animation'); // Update UI - Artwork - use the URL from data-track-artwork var artworkContainer = $('#mini_player_artwork_container'); console.log('jPlayer Mini Player: Track artwork URL:', track.artwork); // Reset progress ring updateProgressRing(0); if (track.artwork && track.artwork.length > 10) { // Use the artwork URL directly from the button data attribute // Format: http://mediaistream.com/audio/image/audio_image//small console.log('jPlayer Mini Player: Setting artwork to:', track.artwork); var imgHtml = 'Album'; artworkContainer.html(imgHtml); // Extract and apply color theme from artwork var img = artworkContainer.find('img')[0]; if (img) { if (img.complete) { var color = extractColorFromImage(img); applyThemeColor(color); } else { img.onload = function() { var color = extractColorFromImage(img); applyThemeColor(color); }; } } } else if (trackId) { // Fallback: build image URL using Jamroom standard format // This matches jrAudio_black_overlay_player.tpl format var imgUrl = jamroomUrl + '/' + jrAudioModuleUrl + '/image/audio_image/' + trackId + '/small'; console.log('jPlayer Mini Player: Using fallback artwork URL:', imgUrl); var imgHtml = 'Album'; artworkContainer.html(imgHtml); // Extract and apply color theme from artwork var img = artworkContainer.find('img')[0]; if (img) { if (img.complete) { var color = extractColorFromImage(img); applyThemeColor(color); } else { img.onload = function() { var color = extractColorFromImage(img); applyThemeColor(color); }; } } } else { console.warn('jPlayer Mini Player: No artwork available - using default'); artworkContainer.html('Album Art'); // Reset to default theme applyThemeColor('#1db954'); } // Use the audioUrl from track data if available, otherwise construct it // The audioUrl comes from JavaScript-constructed URL with proper module path var streamUrl; if (track.audioUrl && track.audioUrl.length > 10) { streamUrl = track.audioUrl; console.log('jPlayer Mini Player: Using provided audioUrl:', streamUrl); } else { // Fallback: construct URL using module URL and track ID streamUrl = jamroomUrl + '/' + jrAudioModuleUrl + '/stream/audio_file/' + trackId + '/key=1/file.mp3'; console.log('jPlayer Mini Player: Constructed stream URL from ID:', trackId); console.log('jPlayer Mini Player: Stream URL:', streamUrl); } // Load media and play - like jrAudio_button.tpl miniPlayer.jPlayer("clearMedia"); miniPlayer.jPlayer("setMedia", { mp3: streamUrl }); miniPlayer.jPlayer("play") // Generate progress waveform (will start after audio begins playing) setTimeout(function() { generateProgressWaveform(); }, 500); // Show mini player $('#mini-player').addClass('active').css('transform', 'translateY(0)'); console.log('jPlayer Mini Player: Playing'); } }; // Handle window resize for waveform canvases $(window).on('resize', function() { if (waveformCanvas) { waveformCanvas.width = waveformCanvas.offsetWidth; waveformCanvas.height = waveformCanvas.offsetHeight; } if (progressWaveformCanvas) { progressWaveformCanvas.width = progressWaveformCanvas.offsetWidth; progressWaveformCanvas.height = progressWaveformCanvas.offsetHeight; drawProgressWaveform(); } }); // Make progress waveform clickable for seeking $(progressWaveformCanvas).on('click', function(e) { if (!miniPlayer.data('jPlayer')) return; var rect = progressWaveformCanvas.getBoundingClientRect(); var x = e.clientX - rect.left; var percent = x / rect.width; var duration = miniPlayer.data('jPlayer').status.duration; if (duration > 0) { var seekTime = duration * percent; miniPlayer.jPlayer('play', seekTime); console.log('Progress Waveform: Seeking to', seekTime.toFixed(2), 'seconds'); } }); // Track mouse position for hover preview on progress waveform $(progressWaveformCanvas).on('mousemove', function(e) { var rect = progressWaveformCanvas.getBoundingClientRect(); progressHoverX = e.clientX - rect.left; drawProgressWaveform(); }); // Clear hover preview when mouse leaves progress waveform $(progressWaveformCanvas).on('mouseleave', function() { progressHoverX = -1; drawProgressWaveform(); }); // Add hover effect to progress waveform $(progressWaveformCanvas).css({ 'cursor': 'pointer' }); // Initialize progress waveform canvas size if (progressWaveformCanvas) { progressWaveformCanvas.width = progressWaveformCanvas.offsetWidth; progressWaveformCanvas.height = progressWaveformCanvas.offsetHeight; console.log('Progress Waveform: Canvas initialized'); } console.log('MINI PLAYER: jPlayer initialized and ready'); // Save current playback state before page unload $(window).on('beforeunload', function() { try { var status = miniPlayer.data('jPlayer').status; if (window.MiniPlayerJPlayer && window.MiniPlayerJPlayer.currentTrack && !status.paused) { var playbackState = { track: window.MiniPlayerJPlayer.currentTrack, position: status.currentTime || 0, isPlaying: true, timestamp: Date.now() }; localStorage.setItem('miniPlayerState', JSON.stringify(playbackState)); console.log('MINI PLAYER: Saved state before unload'); } } catch(e) { console.warn('Could not save state on unload:', e); } }); // Restore playback state for continuous play across pages setTimeout(function() { try { var savedState = localStorage.getItem('miniPlayerState'); console.log('MINI PLAYER: Checking for saved state...', savedState ? 'Found' : 'Not found'); if (savedState) { var state = JSON.parse(savedState); var timeSinceLastPlay = Date.now() - (state.timestamp || 0); console.log('MINI PLAYER: State details:', state); console.log('MINI PLAYER: Time since last play:', timeSinceLastPlay, 'ms'); console.log('MINI PLAYER: Is playing:', state.isPlaying); // Only restore if it was playing recently (within 5 minutes) if (timeSinceLastPlay < 300000 && state.track && state.isPlaying) { console.log('MINI PLAYER: Restoring playback...'); console.log('MINI PLAYER: Track:', state.track.title); console.log('MINI PLAYER: Position:', state.position); // Store track in MiniPlayerJPlayer if (window.MiniPlayerJPlayer) { window.MiniPlayerJPlayer.currentTrack = state.track; } // Update UI immediately $('#mini_player_title').text(state.track.title || 'Unknown Track'); $('#mini_player_artist').text(state.track.artist || 'Unknown Artist'); if (state.track.artwork) { var imgHtml = 'Album'; $('#mini_player_artwork_container').html(imgHtml); } // Show mini player $('#mini-player').addClass('active').css('transform', 'translateY(0)'); // Load and play from saved position if (state.track.audioUrl) { console.log('MINI PLAYER: Loading audio:', state.track.audioUrl); miniPlayer.jPlayer("setMedia", { mp3: state.track.audioUrl }); // Play from saved position setTimeout(function() { if (state.position && state.position > 0) { console.log('MINI PLAYER: Playing from position:', state.position); miniPlayer.jPlayer("play", state.position); } else { console.log('MINI PLAYER: Playing from start'); miniPlayer.jPlayer("play"); } }, 100); } } else { console.log('MINI PLAYER: Not restoring - conditions not met'); if (timeSinceLastPlay >= 300000) console.log('MINI PLAYER: Too old'); if (!state.isPlaying) console.log('MINI PLAYER: Was paused'); } } } catch(e) { console.error('MINI PLAYER: Error restoring playback:', e); } }, 1000); });